/*
* Copyright (c) 2010 Michael Collins
*
* This file is part of TerraFirma.
*
* TerraFirma is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TerraFirma is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with TerraFirma.  If not, see <http://www.gnu.org/licenses/>.
*/

//THIS IS THE SERVER IMPLEMENTATION (dedicated and listen) ONLY!!
#ifndef CLIENT

#include "network_thread.h"
#include <string.h>
#include <iostream>
#include "message_logic.h"

extern engine::mpi MPI;

namespace engine
{

    network_thread::network_thread(boost::asio::io_service& io_service, short port) :
        task_thread<local_network_msg>(name()),
        netpipe(msg_queue.read), service(io_service),
        sock(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port))
    {
        std::cout << "Server started at " << sock.local_endpoint() << std::endl;
        MPI.bind_netpipe(&netpipe);
        //Set all previously-reveived sequences to -1 (a number good enough)
        memset(recv_seqs, 0xFF, sizeof(recv_seqs));

        //Make sure that the server sits at hostid 0
        host server;
        server.id = 0;
        server.endpoint = sock.local_endpoint();
        context::Instance().hosts.add(server);
        //Start the receive process
        receive();
    }

    network_thread::~network_thread()
    {
        //dtor
    }

    void network_thread::wait()
    {
        boost::mutex dummy; //This is probably really stupid of me. Nobody really needs this mutex, we just want to be able to wake a condvar
        boost::unique_lock<boost::mutex> lock(dummy);

        while(msg_queue.isempty() && netpipe.isempty())
            msg_queue.read.wait(lock); //Wait for condition to be true
    }

    void network_thread::F(const boost::thread::id tid)
    {
        running = true;
        while(running)
        {
            wait(); //Blocks until a message comes in from another thread

            //Flush the queue
            while(!msg_queue.isempty())
            {
                process_message(msg_queue.front());
                msg_queue.pop();
            }

            //Send out all messages waiting to be sent to various hosts
            while(!netpipe.isempty())
            {
                send_message(netpipe.front());
                netpipe.pop();
            }
        } //while(running)
        service.stop();
    }

    void network_thread::process_message(const local_network_msg& msg)
    {
        if(msg.header.major_type() == BCAST_CLOSE)
            running = false;

    }

    void network_thread::process_remote_message(const message_header& header_msg)
    {
        //For now, simply pass on message to the MPI where it will be directed where it needs to go
        //Check if it is a broadcast message
        if(header_msg.major_type() >= BCAST_PAUSE)
            MPI.broadcast_local(&header_msg);
        else
            MPI.enqueue_local(&header_msg);
    }

    void network_thread::handle_receive_from(const boost::system::error_code& error, size_t bytes_received)
    {
        std::cout << "Received packet from " << sender << "sequence " << data.header.seq << std::endl;
        if(!error && bytes_received > 0)
        {
            //receive();

            //We just got a packet from another host! Let's process it.
            if(data.header.packet_type == net_header::CONNECTION_REQ)
            {
                //We got a connection request. Right now, we accept any connection request
                //In the case of a connection acceptance, the client gets his hostid from the
                //dest_host field in the net header.
                host new_host;
                new_host.id = context::Instance().hosts.next_idx();
                new_host.endpoint = sender;
                context::Instance().hosts.add(new_host);
                net_header accept(new_host.id, 0);
                //recv_seqs[new_host.id] = data.header.seq; //Record the sequence number from the packet
                accept.packet_type = net_header::CONNECTION_ACPT;


                //Now send the reply back to the new client
                send(sender, &accept, sizeof(net_header));
                return;
            }

            //Check for destination host sanity
            if(data.header.dest_host)
            {
                receive();
                return; //Dest_host should be 0 because we are the server! discard.
            }

            //Anti-cheating measures. make sure that source host is actually the host it claims to be
            if(context::Instance().hosts.at(data.cast().get_src_host()).endpoint != sender)
            {
                std::cout << "Cheater! We got a spoof packet from " << sender << std::endl;
                //discard
                receive();
                return;
            }

            if(data.header.packet_type == net_header::CONNECTION_TERM)
            {
                //Terminate the connection
                hostid deadhost = data.cast().get_src_host();
                std::cout << "Client "<<deadhost<<" is disconnecting.\n";
                //Determine player id from host id
                //Do a linear search? Perhaps make this better with an associative container host->player
                static_heap<game::Player>::const_iterator it = context::Instance().world.players.begin();
                for(; it < context::Instance().world.players.end(); it++)
                {
                    if((*it).host_id == deadhost)
                        break;
                }
                if(it == context::Instance().world.players.end())
                {
                    //Didn't find it. This should hopefully NEVER HAPPEN
                    context::Instance().hosts.remove(deadhost);
                    receive();
                    return;
                }
                Player_Disconnect_Message discon((*it));
                MPI.enqueue_local(&discon.header);
                MPI.enqueue_all_other_hosts(deadhost, &discon.header);
                //Remove host from our records
                context::Instance().hosts.remove(deadhost);
                //Reset the receive and send sequence counts
                recv_seqs[deadhost] = 0xFFFF;
                netpipe.reset(deadhost);
            }

            //If this is a normal packet and is old (out of order) just discard it.
            if(data.header.packet_type == net_header::NORMAL)
            {
                if(IS_NEW_SEQ(data.header.seq, recv_seqs[data.cast().get_src_host()]))
                {
                    process_remote_message(data.cast());
                    recv_seqs[data.cast().get_src_host()] = data.header.seq;
                    receive();
                    return;
                } else
                {
                    receive();
                    return;
                }
            } else if(data.header.packet_type == net_header::ORDERED)
            {
                //Ordered packets need an ack back to the sender so the sender knows that the packet was
                //received
                net_header ack(data.cast().get_src_host(), data.header.seq);
                ack.packet_type = net_header::ACK;
                send(sender, &ack, sizeof(ack));
                if(IS_NEW_SEQ(data.header.seq, recv_seqs[data.cast().get_src_host()]))
                {
                    process_remote_message(data.cast());
                    recv_seqs[data.cast().get_src_host()] = data.header.seq;
                }
                return;
            }
            receive();

        }
        else //if(!error && bytes_received > 0)
            //Perhaps spit an error here, whatevs
            receive();
    }

    void network_thread::handle_send_to(const boost::system::error_code& error, size_t bytes_sent)
    {
        receive();
        printf("%ld bytes sent.\n", bytes_sent);
    }

    void network_thread::send_message(const inter_network_msg& m)
    {
        //Match up the hostid with the remote endpoint
        boost::asio::ip::udp::endpoint ep = context::Instance().hosts.at(m.header.dest_host).endpoint;
        #ifdef DEBUG
        std::cout << m.cast() << "sent to host " << ep << std::endl;
        #endif
        size_t size = m.cast().get_size() + sizeof(net_header); //Total size of the packet, including the header
        send(ep, &m, size); //Send this packet out to the remote host
    }

}
#endif //ifndef CLIENT
